﻿using System;
using System.Collections.Generic;

namespace TLang.Ast
{
    using Values;

    public class CallNode : Node
    {
        public readonly Node Op;
        public readonly Argument Args;
        
        public CallNode(Node op, Argument args, String file, int start, int end, int line, int col) : base(file, start, end, line, col)
        {
            this.Op = op;
            this.Args = args;
        }

        public override Value Interp(Scope s)
        {
            Value opv = this.Op.Interp(s);
            if (opv is Closure closure)
            {
                Scope funScope = new Scope(closure.Env);
                List<NameNode> prams = closure.Fun.Prams;
                
                // set default values for parameters
                if (closure.Properties != null)
                {
                    DeclareNode.MergeDefault(closure.Properties, funScope);
                }

                if (!Args.Positional.IsEmpty() && Args.Keywords.IsEmpty())
                {
                    for (int i = 0; i < Args.Positional.Count; i++)
                    {
                        Value value = Args.Positional[i].Interp(s);
                        funScope.PutValue(prams[i].Id, value);
                    }
                }
                else
                {
                    // try to bind all arguments
                    foreach (NameNode param in prams)
                    {
                        Node actual = Args.Keywords.Get(param.Id);
                        if (actual != null)
                        {
                            Value value = actual.Interp(funScope);
                            funScope.PutValue(param.Id, value);
                        }
                    }
                }
                return closure.Fun.Body.Interp(funScope);
            }
            else if (opv is RecordType template)
            {
                Scope values = new Scope();

                // set default values for fields
                DeclareNode.MergeDefault(template.Properties, values);

                // instantiate
                return new RecordValue(template.Name, template, values);
            }
            else if (opv is PrimFun prim)
            {
                List<Value> args = Node.InterpList(this.Args.Positional, s);
                return prim.Apply(args, this);
            }
            else
            {
                return opv;
            }
        }

        public override String ToString()
        {
            if (Args.Positional.Count != 0)
            {
                return "(" + Op + " " + Args + ")";
            }
            else
            {
                return "(" + Op + ")";
            }
        }
    }
}
